# -*-coding:utf-8-*-
import time
import types
from functools import wraps, partial

"""
Python 的装饰器本质上是一个嵌套函数，它接受被装饰的函数 (func) 作为参数，并返回一个包装过的函数。
(1) 调用装饰器不带参数时：
    @decorator
    def f(x):
        ...
    f(1)
    相当于======>
    f = decorator(f)
    f(1)

(2) 调用装饰器带参数时：
    @decorator(1, 2)
    def f(x):
        ...
    f(1)
    相当于======>
    f = decorator(1, 2)(f)
    f(1)

(3) 被装饰函数或方法的__name__改变的问题
    def decorator(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            ...
        return wrapper
    @decorator
    def f(x):...
不使用wraps的话,f.__name__会变成wrapper(装饰器是用类实现的话f.__name__为decorator类名)
另外用类来实现不带参数的装饰器时wraps的用法为:
    class decorator(object):
        def __init__(self, func):
            wraps(clazz)(self)
    
        def __call__(self, *args, **kwargs):
            return self.__wrapped__(*args, **kwargs)

总结就是最好用嵌套函数实现装饰器，可以避免奇奇怪怪的问题。
"""

# ------------------------------类装饰器----------------------------------------

# 类实现不带参数的类装饰器
"""
def singleton(clazz):
    instance = None
    @wraps(clazz)
    def wrapper(*args, **kwargs):
        nonlocal instance
        if instance is None:
            instance = clazz(*args, **kwargs)
        return instance
    return wrapper
"""
class singleton(object):
    def __init__(self, clazz):
        wraps(clazz)(self)
        self._instance = None  # 注意不能写成类变量

    def __call__(self, *args, **kwargs):
        if self._instance is None:
            self._instance = self.__wrapped__(*args, **kwargs)
        return self._instance


# ------------------------------函数、方法装饰器----------------------------------------

# 类实现不带参数的函数装饰器,利用装饰器实现打印函数运行时间
# 不能装饰类方法,因为被装饰的方法成了TimeIt的实例,调用该方法时触发TimeIt的call方法,不会像调用正常类方法一样自动传入self参数.
class TimeIt(object):
    def __init__(self, func):
        wraps(func)(self)

    def __call__(self, *args, **kwargs):
        t = time.time()
        r = self.__wrapped__(*args, **kwargs)
        print("运行函数{}消耗时间{}s".format(self.__wrapped__.__name__, time.time() - t))
        return r

# 嵌套函数实现不带参数的函数装饰器(不带参数装饰器需要两层嵌套实现)
# 可以直接装饰类方法,若要获取类实例时定义wrapper(self, *args, **kwargs),func(self, *args, **kwargs)
def time_it(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        t = time.time()
        r = func(*args, **kwargs)
        print("运行函数{}消耗时间{}s".format(func.__name__, time.time() - t))
        return r
    return wrapper

# 类实现带参数的函数装饰器,利用装饰器实现指定休眠时间
# 可以直接装饰类方法,若要获取类实例时定义wrapper(func_self, *args, **kwargs),func(func_self, *args, **kwargs)
class TimeSleep(object):
    def __init__(self, t=0.0):
        self.t = t

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("函数{}休眠{}s".format(func.__name__, self.t))
            time.sleep(self.t)
            r = func(*args, **kwargs)
            return r
        return wrapper

# 嵌套函数实现带参数的函数装饰器(带参数装饰器需要三层嵌套实现)
# 可以直接装饰类方法,若要获取类实例时定义inner_wrapper(func_self, *args, **kwargs),func(func_self, *args, **kwargs)
def time_sleep(t=0.0):
    def wrapper(func):
        @wraps(func)
        def inner_wrapper(*args, **kwargs):
            print("函数{}休眠{}s".format(func.__name__, t))
            time.sleep(t)
            r = func(*args, **kwargs)
            return r
        return inner_wrapper
    return wrapper

# 既可以带参数也可以不带参数的装饰器实现(即支持 @sleep(t=1) @sleep 两种形式使用)
def sleep(func=None, *, t=0.0):
    def wrapper(func):
        @wraps(func)
        def inner_wrapper(*args, **kwargs):
            print("函数{}休眠{}s".format(func.__name__, t))
            time.sleep(t)
            r = func(*args, **kwargs)
            return r
        return inner_wrapper
    if func is None:  # func为None时说明是以@sleep(t=1)的形式使用
        return wrapper
    return wrapper(func)

# 类实现不带参数的既能用于函数也能用于类方法的通用函数装饰器
class CommonTimeIt(object):
    def __init__(self, func):
        wraps(func)(self)

    # 当装饰类方法或类实例方法时会触发__get__方法（注意装饰函数或类或静态方法时不会触发该方法）
    def __get__(self, instance, owner):
        # 被装饰方法直接通过类名.类实例方法(实例对象,参数...)调用时instance为None,这时直接返回self就行,因为实例对象已经作为参数传进来了
        if instance is None:
            return self
        else:
            # 手动创建一个绑定方法来使用
            return types.MethodType(self, instance)

    def __call__(self, *args, **kwargs):
        t = time.time()
        r = self.__wrapped__(*args, **kwargs)
        print("运行函数{}消耗时间{}s".format(self.__wrapped__.__name__, time.time() - t))
        return r

# 实现内置的classmethod的功能
class ClassMethod(object):
    def __init__(self, func):
        wraps(func)(self)

    def __get__(self, instance, owner):
        return partial(self.__wrapped__, owner)

# ------------------------------例子----------------------------------------

@singleton  # 本质为A = singleton(A)
class A(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @CommonTimeIt
    def sum_m(self, m):
        return self.x+self.y+m


@TimeIt  # 本质为f = TimeIt(f)
def f(x, y):
    time.sleep(0.1)
    return x + y

@time_it
def f1(x, y):
    time.sleep(0.1)
    return x + y

@TimeSleep(0.2)  # 本质为f2 = TimeSleep(0.1)(f2)
def f2(x, y):
    return x + y

@time_sleep(0.1)
def f3(x, y):
    return x + y

if __name__ == '__main__':
    a = A(1, 2)
    print(a.sum_m(1))
    print(f(1, 2))
    print(f1(1, 2))
    print(f2(1, 2))
    print(f3(1, 2))
